/*
 * Database.cpp
 *
 *  Created on: 13 Jul 2011
 *      Author: allan
 */

#include "Database.h"

// C++ includes
#include <algorithm>
#include <iostream>
#include <fstream>
using namespace std;

// Boost includes
#include <boost/algorithm/string.hpp>
#include <boost/foreach.hpp>
using namespace boost;

typedef vector<string> Block;

void ReadFile(vector<string>& strfile, ifstream& file);
void ReadBlocks(list<Block>& blocks, const vector<string>& strfile);

void ReadSpecies(SpeciesInfo& species, const Block& block);
void ReadSpeciesFormula(SpeciesInfo& species, const Block& block);
void ReadSpeciesName(SpeciesInfo& species, const Block& block);
void ReadSpeciesMolecularWeight(SpeciesInfo& species, const Block& block);
void ReadSpeciesVolumetricWeight(SpeciesInfo& species, const Block& block);
void ReadSpeciesRadius(SpeciesInfo& species, const Block& block);
void ReadSpeciesTags(SpeciesInfo& species, const Block& block);

void ReadReaction(ReactionInfo& reaction, const Block& block);
void ReadReactionReactant(ReactionInfo& reaction, const Block& block);
void ReadReactionProducts(ReactionInfo& reaction, const Block& block);
void ReadReactionEquilibriumData(ReactionInfo& reaction, const Block& block);

const string FindLine(const Block& block, const string& key);

const bool IsReactionBlock(const Block& block);

//-------------------------------------------------------------------------------------------------//

Database::Database()
{}

Database::Database(const string& filename)
{
	ifstream file(filename);
	
	if(!file)
	{
		cout << "Error: could not open file " << filename << "." << endl;
		exit(1);
	}
	
	vector<string> strfile; ReadFile(strfile, file);
	
	list<Block> blocks; ReadBlocks(blocks, strfile);
	
	BOOST_FOREACH(const Block& block, blocks)
	{
		SpeciesInfo sp; ReadSpecies(sp, block); species.push_back(sp);
		
		if(IsReactionBlock(block))
		{
			ReactionInfo reaction; ReadReaction(reaction, block); reactions.push_back(reaction);
		}
	}
}

const Database::SpeciesInfoList& Database::GetSpeciesInfoList() const
{
	return species;
}

const Database::ReactionInfoList& Database::GetReactionInfoList() const
{
	return reactions;
}

const SpeciesInfo Database::GetSpeciesInfo(const string& formula) const
{
	const optional<SpeciesInfo> speciesInfo = FindSpeciesInfo(formula);
	
	// Check if the information data of the species has been successfully found in the database
	if(!speciesInfo)
	{
		cerr << "Error: Cannot retrieve information of species " << formula << ". There is no such species in the database." << endl; exit(0);
	}
	
	return *speciesInfo;
}
	
const ReactionInfo Database::GetReactionInfo(const string& reactantFormula) const
{
	const optional<ReactionInfo> reactionInfo = FindReactionInfo(reactantFormula);
	
	// Check if the information data of the species has been successfully found in the database
	if(!reactionInfo)
	{
		cerr << "Error: Cannot retrieve information of reaction whose reactant is " << reactantFormula << ". There is no such reaction in the database." << endl; exit(0);
	}
	
	return *reactionInfo;
}

const optional<SpeciesInfo> Database::FindSpeciesInfo(const string& formula) const
{
	auto iterSpeciesInfo = std::find_if(species.begin(), species.end(), [&](const SpeciesInfo& species) { return species.formula == formula; });
	
	if(iterSpeciesInfo != species.end())
		return optional<SpeciesInfo>(*iterSpeciesInfo);
	else
		return optional<SpeciesInfo>();
}

const optional<ReactionInfo> Database::FindReactionInfo(const string& reactantFormula) const
{
	auto iterReactionInfo = std::find_if(reactions.begin(), reactions.end(), [&](const ReactionInfo& reaction) { return reaction.reactant == reactantFormula; });
	
	if(iterReactionInfo != reactions.end())
		return optional<ReactionInfo>(*iterReactionInfo);
	else
		return optional<ReactionInfo>();
}
	
const Database& Database::GetInstance()
{
	static Database db("Databases/chess.tdb");
	
	return db;
}

//-------------------------------------------------------------------------------------------------//

void ReadFile(vector<string>& strfile, ifstream& file)
{
	while(!file.eof())
	{
		string line; std::getline(file, line);
		
		//boost::erase_all(line, " ");
		
		boost::trim_if(line, boost::is_any_of(" \r\t"));
		
		if(boost::contains(line, "#") || line == "")
			continue;
		
		strfile.push_back(line);
	}
}

void ReadBlocks(list<Block>& blocks, const vector<string>& strfile)
{
	string current_species_type = "";
	
	uint deep_level = 0;
	
	BOOST_FOREACH(const string& line, strfile)
	{
		if(boost::contains(line, "{") && deep_level == 0)
		{
			current_species_type = boost::erase_all_copy(line, " {");
			
			++deep_level;
			
			continue;
		}
		
		if(boost::contains(line, "{") && deep_level == 1)
		{
			blocks.push_back(Block());
				
			blocks.back().push_back(current_species_type);
		
			++deep_level;
		}
		
		if(deep_level == 2)
			blocks.back().push_back(line);
		
		if(boost::contains(line, "}"))
			--deep_level;
	}
}

void ReadSpecies(SpeciesInfo& species, const Block& block)
{
	ReadSpeciesFormula(species, block);
	ReadSpeciesName(species, block);
	ReadSpeciesMolecularWeight(species, block);
	ReadSpeciesVolumetricWeight(species, block);
	ReadSpeciesRadius(species, block);
	ReadSpeciesTags(species, block);
}

void ReadSpeciesFormula(SpeciesInfo& species, const Block& block)
{
	species.formula = boost::erase_all_copy(block[1], " {");
}

void ReadSpeciesName(SpeciesInfo& species, const Block& block)
{
	string line = FindLine(block, "name");
	
	if(line == "") return;
	
	vector<string> words = SplitLineAndClean(line, " =");
	
	species.name = words[1];
}

void ReadSpeciesMolecularWeight(SpeciesInfo& species, const Block& block)
{
	string line = FindLine(block, "molew.");
	
	if(line == "") return;
	
	vector<string> words = SplitLineAndClean(line, " =");
	
	species.molecularWeight = atof(words[1].c_str());
}

void ReadSpeciesVolumetricWeight(SpeciesInfo& species, const Block& block)
{
	string line = FindLine(block, "vol.weight");
	
	if(line == "") return;
	
	vector<string> words = SplitLineAndClean(line, " =");
	
	species.volumetricWeigth = atof(words[1].c_str());
}

void ReadSpeciesRadius(SpeciesInfo& species, const Block& block)
{
	string line = FindLine(block, "radius");
	
	if(line == "") return;
	
	vector<string> words = SplitLineAndClean(line, " =");
	
	species.radius = atof(words[1].c_str());
}

void ReadSpeciesTags(SpeciesInfo& species, const Block& block)
{
	     if(block[0] == "basis-species")
		species.tags = {"aqueous", "basis"};
	else if(block[0] == "aqueous-species")
		species.tags = {"aqueous"};
	else if(block[0] == "gaseous-species")
		species.tags = {"gaseous"};
	else if(block[0] == "minerals")
		species.tags = {"mineral"};
	else if(block[0] == "redox-couples")
		species.tags = {"aqueous", "redox"};
	else if(block[0] == "surface-sites")
		species.tags = {"aqueous", "surface-site"};
	else
		cout << "Error: could not determine the type of the species " << species.name << "." << endl;
}

void ReadReaction(ReactionInfo& reaction, const Block& block)
{
	ReadReactionReactant(reaction, block);
	ReadReactionProducts(reaction, block);
	ReadReactionEquilibriumData(reaction, block);
}

void ReadReactionReactant(ReactionInfo& reaction, const Block& block)
{
	vector<string> words = SplitLineAndClean(block[1], " {");
	
	reaction.reactant = words[0];
}

void ReadReactionProducts(ReactionInfo& reaction, const Block& block)
{
	string line = FindLine(block, "composition");
	
	vector<string> words = SplitLineAndClean(line, " =,");
	
	for(uint i = 1; i < words.size(); i += 2)
	{
		reaction.stoichiometries.push_back( atof(words[i].c_str()) );
		reaction.products.push_back(words[i + 1]);
	}
}

void ReadReactionEquilibriumData(ReactionInfo& reaction, const Block& block)
{
	string line = FindLine(block, "logK");
	
	vector<string> words = SplitLineAndClean(line, " =,()");
	
	for(uint i = 1; i < words.size(); i += 2)
	{
		reaction.logKi.push_back( -atof(words[i].c_str()) ); // The equilibrium constants log(K) of GeoReact are the negative of CHESS because they write their reactions in the opposite direction we write
		reaction.Ti.push_back( atof(words[i + 1].c_str()) );
	}
}

const string FindLine(const Block& block, const string& key)
{
	string line = "";
	
	for(uint i = 0; i < block.size(); ++i)
		if( boost::contains(block[i], key) )
			line = (boost::contains(block[i], "\\")) ? block[i] + block[i + 1] : block[i];
	
	boost::erase_all(line, "\\");
	
	return line;
}

const bool IsReactionBlock(const Block& block)
{
	string line = FindLine(block, "composition");
	
	return (line != "");
}

